package expression;


import build.Bin.Function;
import vm.baseNativeUseFunction.NativeUseFunction;
import vm.runtime.NextVMRuntime;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Stack;
import java.util.regex.Pattern;

public class MathExpression {
    public static Pattern pattern_eval_math = Pattern.compile("((?<=\\+)|(?=\\+))|((?<=-)|(?=-))|((?<=\\*)|(?=\\*))|((?<=/)|(?=/))|((?<=\\()|(?=\\())|((?<=\\))|(?=\\)))");
    public static Pattern pattern_math_exp = Pattern.compile("(?=[+\\-*/()])|(?<=[+\\-*/()])");
    public static Pattern splitMath = Pattern.compile("(?=[+\\-*/()])|(?<=[+\\-*/()])");


    private static boolean isOperator(String str) {
        return "+".equals(str) || "-".equals(str) || "*".equals(str) || "/".equals(str);
    }
    public static String[] splitMathExpression(String expression) {
        boolean isIn_1 = false; // ( or )
        boolean isIn_2 = false;


        // a("(")

        int In_1_format_number = 0;
        ArrayList<String> TokenArrays = new ArrayList<>();

        StringBuffer tmp_str = new StringBuffer();
        for (int i = 0; i < expression.length() ;i++) {
            /**
             * 先统计逗号进行分割
             */
            char s = expression.charAt(i);
            if (s == '\"' && isIn_1) {
                tmp_str.append(s);
                continue;
            }
            else if (s == '(' && !isIn_2) {
                isIn_1 = true;
                In_1_format_number += 1;
                tmp_str.append(s);
                continue;
            }
            else if (s == ')' && isIn_1 && !isIn_2) {
                In_1_format_number -= 1;
                tmp_str.append(s);
                if (In_1_format_number == 0) {
                    TokenArrays.add(tmp_str.toString());
                    tmp_str = new StringBuffer();
                    isIn_1 = false;
                }
                continue;
            }
            else if (s == '\"' && !isIn_2) {
                isIn_2 = true;
                tmp_str.append(s);
                continue;
            }
            else if (s == '\"' && isIn_2) {
                isIn_2 = false;
                tmp_str.append(s);
                TokenArrays.add(tmp_str.toString());
                tmp_str = new StringBuffer();
                continue;
            }
            else if (( s == '+' || s == '-' || s == '*' || s == '/' || s == '%') && !isIn_2 && !isIn_1) {
                TokenArrays.add(tmp_str.toString());
                tmp_str = new StringBuffer();
                tmp_str.append(s);
                TokenArrays.add(tmp_str.toString());
                tmp_str = new StringBuffer();
                continue;
            }
            else if (( s == '+' || s == '-' || s == '*' || s == '/' || s == '%') && (isIn_2 || isIn_1)) {
                tmp_str.append(s);
                continue;
            }
            else if ((s == '\\' ) && isIn_2) {
                i += 1;
                tmp_str.append(s);
                tmp_str.append(expression.charAt(i));
                continue;
            }
            else  {
                tmp_str.append(s);
                //System.out.println(i+1 + " "+s_str.length);
                if (i + 1 == expression.length()) {
                    TokenArrays.add(tmp_str.toString());
                }
            }
        }
        return TokenArrays.toArray(new String[0]);
    }

    public static double evaluateExpression(String expression , NextVMRuntime nextVMRuntime)
            throws Exception {
        String[] exp = splitMathExpression(expression);
        try {
            for (int i = 0; i < exp.length ;i++) {
                String s = exp[i].trim();
                if (s.isEmpty()) {
                    continue;
                }
                Object value = StringExpression.getValueFromNextVM(s, nextVMRuntime);
                if (value != null) {
                    exp[i] = String.valueOf(value);
                    continue;
                } else if (s.contains("(") && s.contains(")")) {
                    if (NativeUseFunction.IsNativeUseFunctionCode(s)) {
                        String code = s;
                        String function = code.substring(0,code.indexOf("(")).trim();
                        String content = code.substring(code.indexOf("(")+1 , code.lastIndexOf(")")).trim();
                        ArrayList<String> tokens = new ArrayList<>();
                        tokens.add(function);
                        tokens.addAll(Arrays.asList(StringExpression.splitStringTokens(content)));
                        exp[i] = String.valueOf(NativeUseFunction.run(tokens.toArray(new String[0]), nextVMRuntime, s));
                        //System.out.println(NativeUseFunction.run(tokens.toArray(new String[0]), nextVMRuntime, s));
                        continue;
                    }
                    NextVMRuntime UseFunction = CodeFunctionFromRuntime(s, nextVMRuntime);
                    if (UseFunction != null) {
                        UseFunction.run();
                        exp[i] = String.valueOf(UseFunction.ReturnContent);
                        continue;
                    } 
                } else if (s.contains("[") && s.contains("]")) {
                    if (isList(s, nextVMRuntime, exp[i])) {
                    }
                    continue;
                }else {
                    exp[i] = s;
                    continue;
                }
            }
            //return 1;
            return RealEvaluateExpression(exp);
        } catch (Exception e) {
            //e.printStackTrace();
            throw new Exception(e);
        }
    }
    public static NextVMRuntime CodeFunctionFromRuntime(String code , NextVMRuntime nextVMRuntime) {
        try {
            String Name = code.substring(0 , code.indexOf("(")).trim();
            NextVMRuntime UserDefineFunc = NextVMRuntime.getFunctionFromRuntime(Name , nextVMRuntime);
            if (UserDefineFunc != null) {
                String Content = code.substring(code.indexOf("(")+1,code.lastIndexOf(")")).trim();
                //String regex = ",(?=([^\"]*\"[^\"]*\")*[^\"]*$)(?=([^\\(]*\\([^\\)]*\\))*[^\\)]*$)";
                String[] split = StringExpression.splitStringTokens(Content);
                Function.putInputFunction(Name , UserDefineFunc , nextVMRuntime , split);
                UserDefineFunc.AllowRun = true;
                UserDefineFunc.IsInRootFunction = true;
                return UserDefineFunc;
            }else if (Name.contains(".")) {
                String ClassName = Name.substring(0,Name.indexOf(".")).trim();
                String Invoke = Name.substring(Name.indexOf(".")+1).trim();

                String Content = code.substring(code.indexOf("(")+1,code.lastIndexOf(")")).trim();
                //String regex = ",(?=([^\"]*\"[^\"]*\")*[^\"]*$)(?=([^\\(]*\\([^\\)]*\\))*[^\\)]*$)";
                String[] split = StringExpression.splitStringTokens(Content);

                NextVMRuntime ClassFunc = (NextVMRuntime) StringExpression.getValueFromNextVM(ClassName , nextVMRuntime);
                //System.out.println(ClassFunc.Functions.get(Invoke).FunctionInputValue);
                assert ClassFunc != null;
                NextVMRuntime fn = ClassFunc.Functions.get(Invoke);
                fn.IsInRootFunction = true;
                fn.AllowRun = true;
                Function.putInputFunction(Invoke ,fn , ClassFunc , split);
                return ClassFunc.Functions.get(Invoke);
            }else {
                return null;
            }
        }catch (Exception e) {
            return null;
        }
    }

    private static boolean isList(String code , NextVMRuntime nextVMRuntime,String expI) throws Exception {
        String value = null;
        String input = null;
        try {
            value = code.substring(0 , code.indexOf("[")).trim();
            input = code.substring(code.indexOf("[")+1 , code.length() -1).trim();
            Object object = StringExpression.getValueFromNextVM(value , nextVMRuntime);
            if (object != null) {
                int index = (int) MathExpression.evaluateExpression(input , nextVMRuntime);
                ArrayList<Object> arrayList = (ArrayList<Object>) object;
               // System.out.println(arrayList.size()+" "+arrayList);
                expI = String.valueOf(arrayList.get(index));
            } else {
                throw new Exception("Unknown Value: "+value);
            }
            return true;
        }catch (Exception exception) {
            //System.out.println(code+" "+exception.getMessage());
            return false;
        }
        finally {
            //System.out.println(value+" "+input);
        }
    }

    public static double RealEvaluateExpression(String[] tokens) throws Exception
    {
        // 以最大化的优化方式进行表达式求值
        Stack<Double> operandStack = new Stack<>();
        Stack<Character> operatorStack = new Stack<>();


        for (String token : tokens) {
            if (isOperator(token)) {
                while (!operatorStack.isEmpty() &&
                        operatorPrecedence(token.charAt(0)) <= operatorPrecedence(operatorStack.peek())) {
                    processAnOperator(operandStack, operatorStack);
                }
                operatorStack.push(token.charAt(0));
            } else {
                operandStack.push(Double.parseDouble(token));
            }
        }

        while (!operatorStack.isEmpty()) {
            processAnOperator(operandStack, operatorStack);
        }

        if (operandStack.size() != 1) {
            throw new Exception("Invalid expression: " + Arrays.toString(tokens));
        }

        return operandStack.pop();
    }

    public static void processAnOperator(Stack<Double> operandStack, Stack<Character> operatorStack) {
        char operator = operatorStack.pop();
        double operand2 = operandStack.pop();
        double operand1 = operandStack.pop();

        switch (operator) {
            case '+':
                operandStack.push(operand1 + operand2);
                break;
            case '-':
                operandStack.push(operand1 - operand2);
                break;
            case '*':
                operandStack.push(operand1 * operand2);
                break;
            case '%':
                operandStack.push(operand1 % operand2);
                break;
            case '/':
                if (operand2 == 0) {
                    throw new ArithmeticException("Division by zero");
                }
                operandStack.push(operand1 / operand2);
                break;
        }
    }

    public static int operatorPrecedence(char operator) {
        switch (operator) {
            case '+':
            case '-':
                return 1;
            case '*':
            case '/':
            case '%':
                return 2;
        }
        return -1;
    }

}
